/**
 * Copyright (C) 2010  Hylke van der Schaaf
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package hextd.core;

import hextd.Constants.Direction;
import hextd.hex.HexBase;
import hextd.hex.HexEmpty;
import hextd.hex.HexPath;
import hextd.hex.HexTile;
import hextd.hex.HexTransparent;
import hextd.wave.PathNormal;
import hextd.wave.PathfinderWaypoints;
import java.awt.Graphics2D;
import java.awt.geom.Point2D;
import java.awt.geom.Point2D.Double;
import java.util.ArrayList;
import java.util.List;
import com.usoog.tdcore.exception.NoSuchTileException;
import com.usoog.tdcore.gamegrid.GameGrid;
import com.usoog.tdcore.gamegrid.GridPoint;
import com.usoog.tdcore.gamegrid.ScaledCanvas;
import com.usoog.tdcore.gamestate.GameState;

/**
 *
 * @author hylke
 */
public class GameGridImplementation implements GameGrid<HexTile> {

	private GameManagerImplementation gameManager;
	/**
	 * the tiles, ordered as
	 * grid[row][col]
	 */
	private HexTile[][] grid = new HexTile[2][2];
	private int gridWidth = 0;
	private int gridHeight = 0;
	private int lastPaddedLeft = -1;
	//
	private List<String> source = new ArrayList<String>();
	private double maxX = 0.0;
	private double maxY = 0.0;

	public GameGridImplementation(GameManagerImplementation gm) {
		gameManager = gm;
	}

	@Override
	public HexTile getTile(int col, int row) throws NoSuchTileException {
		HexTile tile = null;
		try {
			tile = grid[row][col];
		} catch (NullPointerException e) {
			throwNoSuchTileException(col, row);
		}
		if (tile == null) {
			throwNoSuchTileException(col, row);
		}
		return tile;
	}

	@Override
	public HexTile getTile(GridPoint point) throws NoSuchTileException {
		return getTile(point.col, point.row);
	}

	@Override
	public HexTile getTile(Point2D.Double gameCoordinates) throws NoSuchTileException {
		int row, col;
		double x, y, dx, dy, h, r, hWidth, hHeight;
		h = 1;
		r = HexTile.relativeHexWidth;
		hWidth = HexTile.relativeHexWidth * 2.0;
		hHeight = 1.5;
		x = gameCoordinates.x;
		y = gameCoordinates.y;


		col = (int) Math.round(-0.5 + x / hWidth);
		row = (int) Math.round(-0.5 + y / hHeight);
		dx = x - col * hWidth;
		dy = y - row * hHeight;

		if ((row & 1) == 0) {
			if (dy < (h - (h / r) * dx)) {
				col--;
				row--;
			}
			if (dy < (-h + (h / r) * dx)) {
				row--;
			}
		} else {
			if (dx > r) {
				if (dy < h * 2 - h / r * dx) {
					row--;
				}
			} else {
				if (dy < h / r * dx) {
					row--;
				} else {
					col--;
				}
			}
		}
		return getTile(col, row);
	}

	@Override
	public Double getTileGameCoordinates(GridPoint point, Double target) {
		if (target == null) {
			target = new Double();
		}
		int shift = point.row & 1;
		target.x = HexTile.relativeHexWidth + (point.col * 2 + shift) * HexTile.relativeHexWidth;
		target.y = 1.5 * point.row + 1;
		return target;
	}

	public void fillWith(List<String> gridData) {
		GameState gameState = gameManager.getGameState();
		source = gridData;
		gridHeight = gridData.size();
		String line = gridData.get(0);
		String[] splitData = line.split(" ");
		gridWidth = splitData.length;
		grid = new HexTile[gridHeight][gridWidth];

		int colNr, rowNr;
		int lineNr = -1;
		for (String row : gridData) {
			lineNr++;
			int count = 0;
			for (int i = 0; i < row.length(); i++) {
				colNr = count;
				rowNr = lineNr;
				char chr = row.charAt(i);
				if (chr != ' ') {
					if (colNr <= grid.length - (lineNr & 1)) {
						GridPoint point = new GridPoint(colNr, rowNr);
						switch (chr) {
							case 'b':
								grid[rowNr][colNr] = new HexBase(point, gameState, true);
								break;
							case 'e':
							case 'p':
								grid[rowNr][colNr] = new HexPath(point, gameState);
								break;
							case 'n':
								grid[rowNr][colNr] = new HexEmpty(point, gameState);
								break;
							case 't':
								grid[rowNr][colNr] = new HexTransparent(point, gameState);
								break;
							default:
								System.err.println("GameGrid::fillWith: Unknown hex code: " + chr);
								break;
						}
					}
					count++;
				}
			}
		}
		for (HexTile[] row : grid) {
			for (HexTile h : row) {
				if (h != null) {
					h.initNeighbours();
				}
			}
		}
	}

	@Override
	public int getGridWidth() {
		return gridWidth;
	}

	@Override
	public int getGridHeight() {
		return gridHeight;
	}

	@Override
	public double getMaxGameX() {
		return maxX;
	}

	@Override
	public double getMaxGameY() {
		return maxY;
	}

	@Override
	public void tileChanged() {
		pushGrid();
	}

	@Override
	public void paint(Graphics2D g, int gameTime) {
		for (HexTile[] row : grid) {
			for (HexTile h : row) {
				if (h != null) {
					h.paint(g, gameTime);
				}
			}
		}
	}

	@Override
	public void paintEffects(Graphics2D g, int gameTime) {
		for (HexTile[] row : grid) {
			for (HexTile h : row) {
				if (h != null) {
					h.paintEffects(g, gameTime);
				}
			}
		}
	}

	private void throwNoSuchTileException(int col, int row) throws NoSuchTileException {
		throw new NoSuchTileException("No tile at"
				+ " col " + col
				+ ", row " + row);
	}

	public void resizeGrid(int width, int height, boolean top, boolean left) {
		GameState gameState = gameManager.getGameState();
		int paddingLeft = 0;
		int paddingTop = 0;
		int cutLeft = 0;
		int cutTop = 0;
		int oddLeftPadding = 0;
		try {
			gridWidth = width;
			gridHeight = height;
			reScale();
		} catch (NumberFormatException e) {
		}

		HexTile[][] newGrid = new HexTile[gridWidth][gridHeight];
		if (left) {
			if (newGrid.length > grid.length) {
				paddingLeft = newGrid.length - grid.length;
			} else {
				cutLeft = grid.length - newGrid.length;
			}

		}

		if (top) {
			if (newGrid[0].length > grid[0].length) {
				paddingTop = newGrid[0].length - grid[0].length;
			} else {
				cutTop = grid[0].length - newGrid[0].length;
			}

		}

		if (top && (((paddingTop - cutTop) & 1) == 1)) {
			// padding left needed on odd rows
			oddLeftPadding = -lastPaddedLeft;
			lastPaddedLeft = oddLeftPadding;
		}

		for (int col = cutTop; col < grid[0].length; col++) {
			boolean odd = ((col & 1) == 1);
			if (oddLeftPadding < 0) {
				odd = !odd;
			}

			int rowCount = grid.length - (col & 1);
			int thisPadLeft = odd ? oddLeftPadding : 0;
			if ((col < grid[0].length)
					&& (col >= 0)
					&& (col + paddingTop - cutTop < newGrid[0].length)
					&& (col + paddingTop - cutTop >= 0)) {
				for (int row = cutLeft - thisPadLeft; row < rowCount; row++) {
					if (row < newGrid.length && row >= 0) {
						int newRow = row + paddingLeft - cutLeft + thisPadLeft;
						int newCol = col + paddingTop - cutTop;
						newGrid[newRow][newCol] = this.grid[row][col];
						newGrid[newRow][newCol].setGridLocation(new GridPoint(newCol, newRow));
					}

				}
			}
		}

		for (int col = 0; col < newGrid[0].length; col++) {
			int rowCount = newGrid.length - (col & 1);
			for (int row = 0; row < rowCount; row++) {
				if (newGrid[row][col] == null) {
					newGrid[row][col] = new HexBase(new GridPoint(col, row), gameState, true);
				}

			}
		}
		List<PathfinderWaypoints> paths = gameState.getPaths();
		for (int i = paths.size() - 1; i >= 0; i--) {
			PathNormal p = (PathNormal) paths.get(i);
			p.shift(Direction.SOUTH, paddingTop - cutTop, oddLeftPadding);
			p.shift(Direction.EAST, paddingLeft - cutLeft, oddLeftPadding);
		}

		grid = newGrid;
		pushGrid();
	}

	public void resetScale(ScaledCanvas sc) {
		for (HexTile[] row : grid) {
			for (HexTile h : row) {
				if (h != null) {
					h.resetScale(sc);
				}
			}
		}
	}

	/**
	 * Turns the current grid into a List<String> 
	 */
	private void pushGrid() {
		if (source != null) {
			source.clear();
			String[] lines = new String[Math.max(grid[0].length, grid[1].length)];
			for (int i = 0; i < lines.length; i++) {
				lines[i] = "";
			}
			for (HexTile[] col : grid) {
				int i = 0;
				for (HexTile h : col) {
					if (h != null) {
						lines[i] += h.getShortName() + " ";
					}
					i++;
				}
			}
			for (String line : lines) {
				source.add(line.trim());
			}
		}
	}

	private void reScale() {
		maxX = (2.0 * HexTile.relativeHexWidth * gridWidth);
		maxY = (1.5 * (gridHeight + 0.5));
	}
}
